package com.ymx.darling.executor.mapping.handler;

import com.ymx.darling.bean.entity.listener.SetListener;
import com.ymx.darling.executor.mapping.metadata.MetaData;
import com.ymx.darling.bean.sql.Statement;
import com.ymx.darling.executor.mapping.metadata.MetaDataManager;
import com.ymx.darling.executor.mapping.result.AutoResultMapBuilder;
import com.ymx.darling.executor.mapping.result.ManualResultMapBuilder;
import com.ymx.darling.executor.mapping.result.ResultMap;
import com.ymx.darling.executor.mapping.result.ResultMapBuilder;
import com.ymx.darling.executor.mapping.relation.RelationFieldInfo;
import com.ymx.darling.executor.mapping.relation.RelationType;
import com.ymx.darling.type.StringHandler;
import com.ymx.darling.type.TypeHandler;
import com.ymx.darling.util.CollectionUtil;
import com.ymx.darling.util.ReflectUtil;
import com.ymx.darling.util.StringUtil;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * javabean映射器<br/>
 * resultSet的每条记录映射为一个javabean
 * 将所有javabean放入list集合
 *
 * @author 爱java的小于
 * @param <T>
 */
public class BeanResultSetHandler<T> implements ResultSetHandler<T> {
    /**
     * sql封装类
     */
    private final Statement statement;

    /**
     * 游标映射时的映射
     */
    private ResultMap cursorResultMap;


    public BeanResultSetHandler(Statement statement) {
        this.statement = statement;
    }


    @Override
    public List<T> handle(ResultSet resultSet) throws SQLException {
        List<T> resultList = new ArrayList<>();
        // 构建ResultMap
        ResultMap resultMap = this.createResultMap(resultSet);
        // 判断结果集和实体存在映射关系
        if (Objects.nonNull(resultMap)) {
            // 判断结果集为嵌套结果集
            if (resultMap.hasRelationJoinField()) {
                statement.setUseCache(false);// 涉及嵌套映射时数据不进行缓存
                this.handleNestedResultSet(resultMap, resultSet, resultList);
            } else {
                this.handleSimpleResultSet(resultMap, resultSet, resultList);
            }
            // 处理关联查询
            if (CollectionUtil.notEmpty(resultList) && statement.hasRelationProperty()) {
                this.handRelationQuery(statement, resultList);
            }
        }
        return resultList;
    }


    @Override
    public T handleCursor(ResultSet resultSet) throws SQLException {
        if (this.cursorResultMap == null) {
            this.cursorResultMap = this.createResultMap(resultSet);
        }
        return this.doMapping(this.cursorResultMap, resultSet);
    }


    /**
     * 根据实体类的信息从ResultSet中构建ResultMap
     * @param resultSet resultSet
     * @return ResultMap
     */
    private ResultMap createResultMap(ResultSet resultSet)throws SQLException {
        // 取出结果集中去重后的元数据
        List<MetaData> resultSetMetaData = MetaDataManager.getMetaDataList(resultSet);
        LinkedHashSet<MetaData> metaDataList = new LinkedHashSet<>(resultSetMetaData);
        ResultMapBuilder resultMapBuilder = this.statement.isCustomMapping() ? new ManualResultMapBuilder() : new AutoResultMapBuilder();
        // 构建ResultMap
        return resultMapBuilder.buildResultMap(this.statement, metaDataList);
    }


    /**
     * 简单类型属性的映射
     * @param resultSet 结果集
     * @throws SQLException 可能会抛出此异常
     */
    private void handleSimpleResultSet(ResultMap resultMap, ResultSet resultSet, List<T> resultList)throws SQLException{
        while (resultSet.next()) {
            T result = this.doMapping(resultMap, resultSet);
            resultList.add(result);
        }
    }


    /**
     * 执行映射处理
     * @param resultMap 映射器
     * @param resultSet 结果集
     */
    protected void handleNestedResultSet(ResultMap resultMap, ResultSet resultSet, List<T> entityList)throws SQLException {
        final Set<String> entitySet = new HashSet<>(); // 去重映射类型
        while (resultSet.next()) {
            final ResultObjectCache rowValueCache = this.handRowValue(resultMap, resultSet);
            if (!entitySet.contains(rowValueCache.getKey())) {
                entitySet.add(rowValueCache.getKey());
                entityList.add((T)rowValueCache.getValue());
            }
        }
    }


    /**
     * 处理字段嵌套子查询
     */
    private void handRelationQuery(Statement statement, List<T> list) {
        // 执行待查询的级联字段
        statement.getQueryPropertyList().forEach(relationField -> {
            RelationFieldInfo fieldInfo = relationField.getRelationFieldInfo();
            fieldInfo.getRelationLoader().loader(statement, relationField, list);
        });
    }


    /**
     * 映射结果集的记录行并返回
     *
     * @param resultMap 类似于Mybatis中ResultMap对象
     * @param resultSet 结果集数据
     * @return Object 映射后的实体
     */
    private ResultObjectCache handRowValue(ResultMap resultMap, ResultSet resultSet) throws SQLException {
        // 优先判断使用缓存中实例
        Object rowObject;
        String key = this.createResultKey(resultMap, resultSet);
        if (resultMap.hasResultValue(key)) {
            rowObject = resultMap.getResultValue(key);
        // 映射新的对象并放入缓存
        } else {
            rowObject = this.doMapping(resultMap, resultSet);
            resultMap.putResultValue(key, rowObject);
        }

        // 处理嵌套结果集字段
        if (resultMap.hasRelationJoinField()) {
            for (Map.Entry<ResultMap, RelationType> entry : resultMap.getResultMaps().entrySet()) {
                ResultMap reMap = entry.getKey();
                RelationType aType = entry.getValue();
                ResultObjectCache cache = this.handRowValue(reMap, resultSet);
                //映射数据不为null时进行映射
                if (StringUtil.isNoNull(cache.getKey())) {
                    Object associationFieldValue = cache.getValue();
                    this.putAssociationFieldValueToResult(rowObject, associationFieldValue, reMap.getField(), aType);
                }
            }
        }
        //将映射后的实体连同key一起返回
        return new ResultObjectCache(key, rowObject);
    }


    /**
     * 为实体类的关联字段赋值
     * @param result result
     * @param field 关联字段的Field实例
     * @param associationFieldValue 关联字段的值
     * @param relationType 关联类型
     */
    private void putAssociationFieldValueToResult(Object result, Object associationFieldValue, Field field, RelationType relationType) {
        if (relationType == RelationType.ONE_TO_ONE) {
            ReflectUtil.setValue(field, result, associationFieldValue);
        } else {
            List<Object> list = ReflectUtil.getValue(field, result);
            if (Objects.isNull(list)) {
                list = ReflectUtil.newInstanceList(field.getType());
                ReflectUtil.setValue(field, result, list);
            }

            list.add(associationFieldValue);
        }
    }


    /**
     * 映射实体的简单类型字段
     * @param resultSet 结果集
     * @param resultMap 结果集映射器
     * @return resultObjectCache 返回映射后的数据及其缓存
     */
    private T doMapping(ResultMap resultMap, ResultSet resultSet) throws SQLException {
        final List<MetaData> metaDataList = resultMap.getMetaDataList();
        final T result = ReflectUtil.newInstance(resultMap.getModule());
        final List<SetListener> setListenerList = resultMap.getBeanInfo().getOnSetListeners();
        // 映射字段数据
        for (int i = metaDataList.size() - 1; i > -1; --i) {
            final MetaData md = metaDataList.get(i);
            final Field field = md.getField();
            final TypeHandler<?> convertHandler = md.getTypeHandler();
            Object value = convertHandler.getResult(resultSet, md.getIndex());
            // 做onSet侦听处理
            if (!setListenerList.isEmpty()) {
                for (int j = setListenerList.size() - 1; j > -1; --j) {
                    SetListener setListener = setListenerList.get(j);
                    value = setListener.onSet(result, field.getName(), value);
                }
            }
            // 值为null默认不处理
            if (value != null) {
                ReflectUtil.setValue(field, result, value);
            }
        }
        return result;
    }


    /**
     * 构建映射类型的缓存
     * @param resultMap resultMap
     * @param resultSet resultSet
     * @return cacheKey
     */
    private String createResultKey(ResultMap resultMap, ResultSet resultSet) throws SQLException {
        final TypeHandler<String> stringTypeHandler = new StringHandler();
        final List<MetaData> metaDataList = resultMap.getMetaDataList();
        final StringBuilder valueKeyBuilder = new StringBuilder();
        // 判断存在id直接将id作为缓存的key 这么做可以提升映射效率
        if (Objects.nonNull(resultMap.getIdMetaData())) {
            MetaData idMD = resultMap.getIdMetaData();
            valueKeyBuilder.append(stringTypeHandler.getResult(resultSet, idMD.getIndex()));
        } else {
            // 将所有已映射字段数据作为key
            for (int i = metaDataList.size() - 1; i > -1; --i) {
                MetaData md = metaDataList.get(i);
                Object key = stringTypeHandler.getResult(resultSet, md.getIndex());
                if (Objects.nonNull(key)) {
                    valueKeyBuilder.append(key);
                }
            }
        }
        return valueKeyBuilder.toString();
    }


    private static class ResultObjectCache {
        private final String key;
        private final Object value;

        public ResultObjectCache(String key, Object value) {
            this.key = key;
            this.value = value;
        }

        public Object getValue() {
            return this.value;
        }

        public String getKey() {
            return this.key;
        }

        public String toString() {
            return "Cache{key=" + this.key + ", value=" + this.value + '}';
        }
    }
}
